# encoding: utf-8

require 'rest_client'
require 'json'
require 'enumerator'

module RJMetrics

  class Client


   # default RJMetrics Data Import API url
   API_BASE = "https://connect.rjmetrics.com/v2"
   # RJMetrics Sandbox API url
   SANDBOX_BASE = "https://sandbox-connect.rjmetrics.com/v2"
   # Datapoints to push at a time
   BATCH_SIZE = 100
   # Timout Seconds
   DEFAULT_TIMEOUT_SECONDS = 10

    # Constructs a Client instance if it receives valid arguments or will raise an ArgumentError.
    #
    # @param client_id [Integer] your RJMetrics Client ID
    # @param api_key [String] your RJMetrics API Key
    # @param timeout_in_seconds [Integer] seconds to wait for API responses
    def initialize(client_id, api_key, timeout_in_seconds = DEFAULT_TIMEOUT_SECONDS)
      validateConstructorArgs(client_id, api_key, timeout_in_seconds)
      @client_id = client_id
      @api_key = api_key
      @timeout_in_seconds = timeout_in_seconds
    end

    # Checks if the provided Client ID and API Key are valid.
    def authenticated?
      begin
        makeAuthAPICall()
      rescue InvalidRequestException
        return false
      end
      return true
    end

    # Sends data to RJMetrics Data Import API in batches of 100.
    #
    # @param table_name [String] the table name you wish to store the data
    # @param data [Hashamp] or Array of Hashmaps of data points that will get sent
    # @param url [String] Import API url
    # @return [Array] results of each request to RJMetrics Data Import API
    def pushData(table_name, data, url = API_BASE)
      responses = Array.new
      validatePushDataArgs(table_name, data, url)

      if not data.is_a? Array
        data = Array.[](data)
      end

      data.each_slice(BATCH_SIZE) {|batch_data|
        responses << makePushDataAPICall(table_name, batch_data, url)
      }
      return responses
    end

    private

    def validateConstructorArgs(client_id, api_key, timeout_in_seconds)
      if not client_id.is_a? Integer or client_id <= 0
        raise ArgumentError, "Invalid client ID: #{client_id} -- must be a positive integer."
      end

      if not timeout_in_seconds.is_a? Integer or timeout_in_seconds <= 0
        raise ArgumentError, "Invalid timeout: #{timeout_in_seconds} -- must be a positive integer."
      end

      if not api_key.is_a? String
        raise ArgumentError, "Invalid API key: #{api_key} -- must be a string."
      end
    end

    def validatePushDataArgs(table_name, data, url)
      if not data.is_a? Hash and not data.is_a? Array
        raise ArgumentError, "Invalid data -- must be a valid Ruby Hash or Array."
      end

      if not table_name.is_a? String
        raise ArgumentError, "Invalid table name: '#{table_name}' -- must be a string."
      end

      if not url.is_a? String
        raise ArgumentError, "Invalid url: '#{url}' -- must be a string."
      end
    end

    # Authenticates with the RJMetrics Data Import API
    def makeAuthAPICall(url = API_BASE)
      request_url = "#{url}/client/#{@client_id}/authenticate?apikey=#{@api_key}"
      begin
        response = RestClient.get(request_url)
        return response
      rescue RestClient::Exception => error
        begin
          response = JSON.parse(error.response)

          unless response
            raise InvalidRequestException, "The Import API returned: #{error.http_code} #{error.message}"
          end

          raise InvalidRequestException,
            "The Import API returned: #{response['code']} #{response['message']}. Reasons: #{response['reasons']}"
        rescue JSON::ParserError, TypeError => json_parse_error
          raise InvalidResponseException,
            "RestClientError: #{error.class}\n Message: #{error.message}\n Response Code: #{error.http_code}\n Full Response: #{error.response}"
        end
      end
    end

    def makePushDataAPICall(table_name, data, url = API_BASE)
      request_url = "#{url}/client/#{@client_id}/table/#{table_name}/data?apikey=#{@api_key}"

      begin
        response = RestClient.post(
          request_url,
          data.to_json,
          {
            :content_type => :json,
            :accept => :json,
            :timeout => @timeout_in_seconds
          }
        )
        return response
      rescue RestClient::Exception => error
        begin
          response = JSON.parse(error.response)

          unless response
            raise InvalidRequestException, "The Import API returned: #{error.http_code} #{error.message}"
          end

          raise InvalidRequestException,
            "The Import API returned: #{response['code']} #{response['message']}. Reasons: #{response['reasons']}"
        rescue JSON::ParserError, TypeError => json_parse_error
          raise InvalidResponseException,
            "RestClientError: #{error.class}\n Message: #{error.message}\n Response Code: #{error.http_code}\n Full Response: #{error.response}"
        end
      end
    end

    class UnableToConnectException < RuntimeError
    end
    class InvalidRequestException < RuntimeError
    end
    class InvalidResponseException < RuntimeError
    end

  end
end
